/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package com.inspur.edp.lcm.metadata.common.util.process;

import com.inspur.edp.lcm.metadata.common.FileServiceImp;
import com.inspur.edp.lcm.metadata.common.Utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
public class AbstractProcessUtil implements ProcessUtil {
    public final int SUCCESS = 0;            // 表示程序执行成功

    public final String SUCCESS_MESSAGE = "程序执行成功。";

    public final String ERROR_MESSAGE = "程序执行出错。";

    public String JAVA_HOME;

    protected String killByPidCmd = "taskkill /F /PID ";

    public String START_CMD_OLD = "startup-win.cmd";

    public String START_CMD = "startup.cmd";

    public String SCRIPT_SUFFIX = ".cmd";

    public String START_CMD_DEBUGGER = ".startup-debugger-win.cmd";

    public String cmdPrefix = "cmd /c start ";

    protected String getPidsByPortCmd = "netstat -ano | findstr ";

    protected String getJavaProcessesCmd = "TASKLIST /NH /FO CSV | findstr java";

    @Override
    public void execGracefully(String cmd, String directory) {
        try {
            Process process;
            if (directory != null) {
                process = Runtime.getRuntime().exec(cmd, null, new File(directory));
            } else {
                process = Runtime.getRuntime().exec(cmd);
            }

            List<String> inputs = readProcessOutput(process);
            List<String> errors = readProcessError(process);

            // 等待程序执行结束并输出状态
            int exitCode = process.waitFor();

            if (exitCode == SUCCESS) {
                log.info(SUCCESS_MESSAGE);
            } else {
                log.info("在" + directory + "执行程序 " + cmd + " 出错。");
                if (!CollectionUtils.isEmpty(errors)) {
                    throw new RuntimeException(ERROR_MESSAGE + errors.stream().collect(Collectors.joining()));
                }
                checkSubprocessError(inputs);
                throw new RuntimeException(ERROR_MESSAGE + exitCode);
            }
        } catch (IOException | InterruptedException e) {
            throw new RuntimeException(ERROR_MESSAGE + cmd, e);
        }
    }

    protected void checkSubprocessError(List<String> inputs) {
        String error = "";
        for (String input : inputs) {
            if (input.startsWith("java.lang.RuntimeException:") || input.startsWith("Caused by:")) {
                error += input.replace("java.lang.RuntimeException:", "").replace("Caused by:", "");
            }
        }
        if (!StringUtils.isEmpty(error)) {
            throw new RuntimeException(ERROR_MESSAGE + error);
        }
    }

    @Override
    public List<String> exec(String cmd) {
        List<String> inputs;
        try {
            Process process = Runtime.getRuntime().exec(cmd);
            inputs = read(process.getInputStream(), System.out);
        } catch (IOException e) {
            throw new RuntimeException(ERROR_MESSAGE + cmd, e);
        }
        return inputs;
    }

    @Override
    public String getJavaHome() {
        if (JAVA_HOME != null) {
            return JAVA_HOME;
        }

        JAVA_HOME = System.getenv("JAVA_HOME");
        JAVA_HOME = JAVA_HOME == null ? System.getProperty("java.home") : JAVA_HOME;

        if (Utils.isNullOrEmpty(JAVA_HOME)) {
            throw new RuntimeException("请配置系统变量JAVA_HOME");
        }

        if (!new File(JAVA_HOME).isAbsolute()) {
            JAVA_HOME = new File(FileServiceImp.combinePath(Utils.getBasePath(), JAVA_HOME)).getAbsolutePath();
        }

        return JAVA_HOME;
    }

    // 通过获取当前运行主机的pidName，截取获得他的pid
    @Override
    public int getCurrentPid() {
        RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
        String pidName = runtime.getName();// 5296@dell-PC
        int pid = Integer.parseInt(pidName.substring(0, pidName.indexOf("@")));
        return pid;
    }

    @Override
    public List<SimpleProcess> getProcessesByFilter(Filter filter) {
        List<SimpleProcess> processes = getJavaProcesses();
        if (processes == null) {
            return null;
        }

        Stream<SimpleProcess> stream = processes.stream();
        if (filter.getProcessID() != 0) {
            stream = stream.filter(process -> process.getPid() == filter.getProcessID());
        }
        return stream.collect(Collectors.toList());
    }

    // 根据Pid将杀死进程
    @Override
    public void killProcessByPid(int pid) {
        execFast(killByPidCmd + pid);
    }

    @Override
    public void open(String path) {
        exec(cmdPrefix + path);
    }

    @Override
    public void restart(String serverPath) {
        serverPath = Utils.isNullOrEmpty(serverPath) ? Utils.getBasePath() : serverPath;
        List<SimpleProcess> javaProcesses = getProcessesByFilter(new Filter("java", serverPath));

        if (javaProcesses != null && javaProcesses.size() > 1) {
            int currentPid = getCurrentPid();
            javaProcesses.forEach(process -> {
                if (process.getPid() > 0 && process.getPid() != currentPid) {
                    killProcessByPid(process.getPid());
                }
            });

            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {}
        }

        start(serverPath, null);
    }

    @Override
    public Process start(String serverPath, String[] envp) {
        serverPath = Utils.isNullOrEmpty(serverPath) ? Utils.getBasePath() : serverPath;
        return exec(getStartCmd(serverPath), envp, new File(serverPath));
    }

    @Override
    public List<Integer> getPidsByPort(int port) {
        try {
            List<String> inputs = exec(getPidsByPortCmd + port);
            List<Integer> pids = parsePids(inputs);
            return pids;
        } catch (Exception e) {
            throw new RuntimeException("获取pid失败：" + port, e);
        }
    }

    @Override
    public boolean isPortAvailable(int port) {
        try (Socket ignored = new Socket("localhost", port)) {
            return false;
        } catch (IOException ignored) {
            return true;
        }
    }

    @Override
    public boolean isPidExist(int pid) {
        List<SimpleProcess> processesByFilter = getProcessesByFilter(new Filter(pid));
        if (!CollectionUtils.isEmpty(processesByFilter)) {
            return true;
        }
        return false;
    }

    @Override
    public String getNameByPid(int pid) {
        List<SimpleProcess> processesByFilter = getProcessesByFilter(new Filter(pid));
        if (!CollectionUtils.isEmpty(processesByFilter)) {
            return processesByFilter.get(0).getName();
        }
        return null;
    }

    @Override
    public boolean checkPath(String path) {
        return true;
    }

    protected List<Integer> parsePids(List<String> inputs) {
        // TCP    0.0.0.0:5005           0.0.0.0:0              LISTENING       64788
        if (CollectionUtils.isEmpty(inputs)) {
            return null;
        }
        List<Integer> pids = new ArrayList<>();
        inputs.forEach(input -> {
            int index = input.lastIndexOf(" ");
            String sc = input.substring(index);
            pids.add(Integer.parseInt(sc.trim()));
        });
        return pids;
    }

    /**
     * 打印进程输出
     *
     * @param process 进程
     */
    protected List<String> readProcessOutput(final Process process) {
        // 将进程的正常输出在 System.out 中打印，进程的错误输出在 System.err 中打印
        return read(process.getInputStream(), System.out);
    }

    /**
     * 打印进程输出
     *
     * @param process 进程
     */
    protected List<String> readProcessError(final Process process) {
        // 将进程的正常输出在 System.out 中打印，进程的错误输出在 System.err 中打印
        return read(process.getErrorStream(), System.err);
    }

    // 读取输入流
    protected List<String> read(InputStream inputStream, PrintStream out) {
        List<String> inputs = new ArrayList<>();
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
            String line;
            while ((line = reader.readLine()) != null) {
                out.println(line);
                inputs.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return inputs;
    }

    protected String getStartCmd(String serverPath) {
        String startCmd;
        serverPath = Utils.isNullOrEmpty(serverPath) ? Utils.getBasePath() : serverPath;
        if (FileServiceImp.isEqual(serverPath, Utils.getBasePath())) {
            startCmd = FileServiceImp.combinePath(serverPath, START_CMD);
            if (!new File(startCmd).exists()) {
                startCmd = FileServiceImp.combinePath(serverPath, START_CMD_OLD);
            }
            if (!new File(startCmd).exists()) {
                File[] files = new File(serverPath).listFiles((dir, name) -> name.endsWith(SCRIPT_SUFFIX));
                if (files != null && files.length > 0) {
                    startCmd = files[0].getPath();
                }
            }
            if (!new File(startCmd).exists()) {
                throw new RuntimeException("无法找到正确的启动脚本：" + serverPath);
            }
        } else {
            startCmd = FileServiceImp.combinePath(serverPath, START_CMD_DEBUGGER);
        }
        return getCmd(startCmd);
    }

    protected String getCmd(String cmd) {
        return cmdPrefix + cmd;
    }

    protected void execFast(String cmd) {
        try {
            Runtime.getRuntime().exec(cmd);
        } catch (IOException e) {
            throw new RuntimeException(ERROR_MESSAGE + cmd, e);
        }
    }

    protected Process exec(String cmd, String[] envp, File dir) {
        Process process;
        try {
            process = Runtime.getRuntime().exec(cmd, envp, dir);
        } catch (IOException e) {
            throw new RuntimeException(ERROR_MESSAGE + cmd);
        }
        return process;
    }

    protected List<SimpleProcess> getJavaProcesses() {
        List<SimpleProcess> processes = new ArrayList<>();
        List<String> javaProcesses = exec(getJavaProcessesCmd);
        if (!Utils.isNullOrEmpty(javaProcesses)) {
            javaProcesses.forEach(line -> {
                String[] array = line.split(",");
                int pid = Integer.parseInt(array[1].replace("\"", ""));
                if (pid != 0) {
                    processes.add(new SimpleProcess(pid, "java"));
                }
            });
        }
        return processes;
    }

}
